package org.korosoft.findbugs.aggregator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xerces.parsers.SAXParser;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public final class FindBugsParser extends DefaultHandler {

    private static final Log LOG = LogFactory.getLog(FindBugsParser.class);

    private List<Bug> bugs = new ArrayList<Bug>();

    public void parse(File findBugsFile, Callback callback) {
        bugs.clear();
        SAXParser parser = new SAXParser();
        parser.setContentHandler(this);
        try {
            parser.parse(new InputSource(new BufferedInputStream(new FileInputStream(findBugsFile))));
        } catch (SAXException e) {
            LOG.error("Parse exception", e);
            throw new AggregatorException(e);
        } catch (IOException e) {
            LOG.error("IO exception", e);
            throw new AggregatorException(e);
        }
        for (Bug bug : bugs) {
            callback.process(bug);
        }
    }

    private String className;
    private boolean isSrcDir = false;
    private StringBuilder srcDirBuilder;

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        if (localName.equalsIgnoreCase("file")) {
            className = attributes.getValue("classname");
        }
        if (localName.equalsIgnoreCase("BugInstance")) {
            String type = attributes.getValue("type");
            String priority = attributes.getValue("priority");
            String category = attributes.getValue("category");
            String message = attributes.getValue("message");
            String lineNumber = attributes.getValue("lineNumber");

            Bug bug = new Bug();
            bug.setCategory(category.intern());
            bug.setClassName(className.intern());
            bug.setType(type.intern());
            bug.setMessage(message.intern());
            bug.setPriority(priority.intern());
            if (lineNumber.matches("^[0-9]+$")) {
                bug.setStartLine(Integer.parseInt(lineNumber));
                bug.setEndLine(Integer.parseInt(lineNumber));
            } else {
                if (lineNumber.matches("^[0-9]+-[0-9]+$")) {
                    String numbers[] = lineNumber.split("-");
                    bug.setStartLine(Integer.parseInt(numbers[0]));
                    bug.setEndLine(Integer.parseInt(numbers[1]));
                }
            }
            bugs.add(bug);
        }
        if (localName.equalsIgnoreCase("SrcDir")) {
            srcDirBuilder = new StringBuilder();
            isSrcDir = true;
        } else {
            isSrcDir = false;
        }
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        if (isSrcDir) {
            srcDirBuilder.append(ch, start, length);
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        if (localName.equalsIgnoreCase("SrcDir")) {
            File srcDir = new File(srcDirBuilder.toString());
            List<Bug> toRemove = new ArrayList<Bug>();
            for (Bug bug : bugs) {
                if (bug.getSourceFile() == null) {
                    bug.setSourceFile(new File(srcDir, bug.getClassName().replaceAll("\\$[^\\.]+$", "").replaceAll("\\.", File.separator.replaceAll("\\\\", "\\\\\\\\")) + ".java"));
                    if (!bug.getSourceFile().exists()) {
                        LOG.warn("Source file not found: " + bug.getSourceFile().getAbsolutePath());
                        toRemove.add(bug);
                    }
                }
            }
            bugs.removeAll(toRemove);
        }
    }

    public interface Callback {
        void process(Bug bug);
    }

}
